package controller;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

import modelo.Game;
import modelo.Monje;
import modelo.Player;
import modelo.Suspect;
import modelo.listener.GameListener;
import constants.Constants;
import constants.Constants.Colors;
import controller.Protocol.*;
/**
 * this is the main class for the server network controller
 * @author Jose Luis Garcia && Alvaro Blazquez Checa
 *
 */

public class Controller implements GameListener {
	
	private ArrayBlockingQueue<ClientMessageClientHandler> clientMessageList;
	private Game game;
	private static Controller instance = null;
	
	/**
	 * this is a singleTone constructor for the game 
	 * @param game
	 * @return
	 */
	public static Controller createController(Game game) {
		
		if(instance==null) {
			instance =  new Controller(game);
		}
		return instance;
		
	}
	
	/**
	 * this method will start the current selection for the clients player and 
	 * will set the params for the new game 
	 */
	public void startGame() {
		
		askPlayersForInfo();
		
		ClientMessageClientHandler message = null;
		
		while(game.getPlayerList().size()<ClientHandler.clientList.size())
		{
			System.out.println("->Esperando mensajes de clientes");
			try {
				message = clientMessageList.take();  // El hilo principal se bloque a la espera de algun mensaje
			} catch (InterruptedException e) {
				System.out.println(e);
				continue;                  // si el mensaje esta corrupto se salta a la siguiente iteracion del while y se espera un nuevo mensaje
			} 
			System.out.println("->Procesando mensaje de cliente");
			
			ClientHandler client = message.getClient();
			ClientMessage clientMessage = message.getMessage();

			List<String> info = clientMessage.getInfo();
			List<GameListener> listeners = new ArrayList<GameListener>();
			listeners.add(this);
			if(clientMessage.getCommand() == ClientCommands.TAKE_PLAYER_INFO) {
				if(checkName(info.get(0))) {
					game.addPlayer(new Player(info.get(0), Colors.valueOf(info.get(1)), client,listeners));
				}
				else {
					List<String> infoForPlayer = new ArrayList<String>();
					infoForPlayer.add("Nombre repetido, elige otro");
					ServerMessage infoMessage = new ServerMessage(ServerCommands.MESSAGE_FOR_PLAYER);
					infoMessage.setInfolist(infoForPlayer);
					client.sendMessage(infoMessage);
					client.sendMessage(new ServerMessage(ServerCommands.GIVE_ME_PLAYER_INFO));
				}
			}
		}
		
		game.dealCards();  // repartir cartas entre los jugadores en funcion del numero de estos
		game.start();	   // configuracion inicial de la partida	
		sendInitialInfoToPlayers();  //mandar info inicial a los jugadores: cartas y sospechosos...
	
		System.out.println("-> empieza la partida");
		ClientHandler.broadcast(new ServerMessage(ServerCommands.TURN_INFO));
		game.getCurrentPlayer().getClientHandler().sendMessage(new ServerMessage(ServerCommands.YOUR_TURN));
		
		listen(); 
	}
	
	/**
	 * this will add a client player to the game
	 * @param message
	 */
	public void addClientMessage(ClientMessageClientHandler message){
		try {
			clientMessageList.put(message);  // ClientHandler se bloquea si la lista esta llena
		} catch (InterruptedException e) {
			System.out.println(e);
		}
	}
	
	/**
	 * this method will receive message and connections from clients and added to the main threat
	 */
	public void listen(){
		
		while(game.isRunning()){

			System.out.println("->Esperando mensajes de clientes");

			ClientMessageClientHandler message;
			try {
				message = clientMessageList.take();  // El hilo principal se bloquea a la espera de algun mensaje
			} catch (InterruptedException e) {
				System.out.println(e);
				continue;                            //si el hilo principal se despierta por una interrupcion externa se vulveal comienzo del while
			} 

			ClientHandler client = message.getClient();
			ClientMessage clientMessage = message.getMessage();
			
			System.out.println("->Procesando mensaje del cliente " + getPlayerFromClient(client).getPlayerName()+ " | tipo: " + clientMessage.getCommand().toString());

			List<String> info = clientMessage.getInfo();  // informacion contenida en el mensaje que manda el cliente

			switch(clientMessage.getCommand()){
			
			case END:
			{
				client.disconnect();
			}
			break;
			
			case END_TURN:
			{
				if(game.getCurrentPlayer() == getPlayerFromClient(client)){
					game.nextPlayer();	
				}
				else{
					// no es el turno del cliente que manda el mensaje
				}
			}
			break;
			
			case CHAT:
			{
				ServerMessage serverMessage = new ServerMessage(ServerCommands.CHAT);
				Player p = getPlayerFromClient(client);
				List<String> infoToSend = new ArrayList<String>();
				infoToSend.add(p.getPlayerName());
				infoToSend.addAll(info);
				serverMessage.setInfolist(infoToSend);
				client.broadcastFromClient(serverMessage);
			}
			break;
			
			case GIVE_ME_MY_CARDS_INFO:
				break;
			
			case MY_MOVE:
			{
				if(game.getCurrentPlayer() == getPlayerFromClient(client)){
					Iterator<String> itr = info.iterator();
					if(itr.hasNext())
						movePlayer(itr.next());
					else{
						//faltan argumentos en info
					}
				}
				else{
					// no es el turno del cliente que manda el mensaje
				}
			}
			break;
			
			case ASK_PLAYER:
			{
				String playerName = info.get(0);	
				Player playerToAsk = null;
				
				for(Player p : game.getPlayerList()) {
					if(p.getPlayerName().equalsIgnoreCase(playerName)) {
						playerToAsk = p;
					}
				}
				
				if(playerToAsk!=null) {
					
					if(!playerToAsk.votoSilencio()) {

						ServerMessage serverMessage = new ServerMessage(ServerCommands.ASK_PLAYER_FOR_QUESTIONS);
						Player sender = getPlayerFromClient(client);
						List<String> infoToSend = new ArrayList<String>();
						infoToSend.add(sender.getPlayerName());
						serverMessage.setInfolist(infoToSend);
						playerToAsk.getClientHandler().sendMessage(serverMessage);

					}
					
					else {
						
						ServerMessage serverMessage = new ServerMessage(ServerCommands.NEGATIVE_ANSWER_FROM_PLAYER);
						List<String> infoToSend = new ArrayList<String>();
						infoToSend.add(playerToAsk.getPlayerName());
						serverMessage.setInfolist(infoToSend);
						game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
						
					}
				}
			}
			break;
			
			case ANSWER_PLAYER:
			{
				String answer = info.get(0);
				Player askedPlayer = getPlayerFromClient(client);
				
				if(answer.equalsIgnoreCase("SI")){
					
					askedPlayer.setSilenceVote();
					
					ServerMessage serverMessage = new ServerMessage(ServerCommands.NEGATIVE_ANSWER_FROM_PLAYER);
					List<String> infoToSend = new ArrayList<String>();
					infoToSend.add(askedPlayer.getPlayerName());
					serverMessage.setInfolist(infoToSend);
					game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
					
				}
				else if(answer.equalsIgnoreCase(("NO"))) {
					
					ServerMessage serverMessage = new ServerMessage(ServerCommands.AFIRMATIVE_ANSWER_FROM_PLAYER);
					List<String> infoToSend = new ArrayList<String>();
					infoToSend.add(askedPlayer.getPlayerName());
					serverMessage.setInfolist(infoToSend);
					game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
				}
			}
			break;
			
			case QUESTION_SEND: 
				
				String playerName = info.remove(0);
				Player playerAnswering=null, playerAsking=null;	
				for(Player p : game.getPlayerList())
					if(p.getPlayerName().equalsIgnoreCase(playerName)){
						playerAnswering=p;
						playerAsking = getPlayerFromClient(client);
						game.procesQuestions(info, playerAsking, playerAnswering);
					}
				break;

			case SEND_REVELATION:

				String type = info.get(0);
				Player p = getPlayerFromClient(client);

				if(type.equalsIgnoreCase("revelacion")) 
					game.makeRevelation(p, info.get(2));

				else if(type.equalsIgnoreCase("acusacion")) 
					game.makeAcusation(p, info.get(2));

				break;
				
			case PLAY_CARD:
				game.playCard(info.get(0),info.get(1),info.get(2),info.get(3));
				break;
			
			case TAKE_PLAYER_INFO:				
			{
			//agrego funcionalidad de las cartas para cada caso
			//				if(game.getCurrentPlayer() == getPlayerFromClient(client)){
			//					if(game.getCurrentPlayer().getUbication().getSquareName().toLowerCase().equals("biblioteca"))
			//						game.getInstance().getLibraryCard();
			//					//falta por conectar a la funcionalidad dependiendo del tipo de carta que tenga	
			}
			break;
			
			default:
				break;
			}
		}
		
	}
	

	//EVENTOS LISTENER
	/**
	 * this will set a player in a place
	 */
	@Override
	public void playerHasMoved(int square,int player) {
		
		List<String> infoList = new ArrayList<String>();
		infoList.add(String.valueOf(square));
		infoList.add(String.valueOf(player));
		if(Game.getInstance().getCurrentPlayer()!= null)
			infoList.add(Game.getInstance().getCurrentPlayer().getUbication().getSquareName());
		else
			infoList.add("No ubicado");
		ServerMessage message = new ServerMessage(ServerCommands.NEW_MOVE);
		message.setInfolist(infoList);
		ClientHandler.broadcast(message);
		
	}
	
	@Override
	/**
	 * this will initiate the meeting panel in client
	 */
	public void meeting() {
		
		Player currentPlayer = game.getCurrentPlayer();
		List<String> infoList = new ArrayList<String>();
		
		for(Player p : currentPlayer.getUbication().getPlayerList()) {
			if(p != currentPlayer) {
				infoList.add(String.valueOf(p.getPlayerNumber()));
				infoList.add(p.getPlayerName());
			}
		}
		
		ServerMessage message = new ServerMessage(ServerCommands.MEETING);
		message.setInfolist(infoList);
		currentPlayer.getClientHandler().sendMessage(message);
		
	}
	
	@Override
	/**
	 * this will notify the new turn to the player
	 */
	public void nextTurn() {
		game.getCurrentPlayer().getClientHandler().sendMessage(new ServerMessage(ServerCommands.YOUR_TURN));
	}
	
	@Override
	/**
	 * this will add a new player
	 */
	public void newPlayer(int player) {
		
		ServerMessage message = new ServerMessage(ServerCommands.NEW_PLAYER);
		List<String> info = new ArrayList<String>();
		info.add(String.valueOf(player));
		message.setInfolist(info);
		ClientHandler.broadcast(message);
		
	}
	@Override
	/**
	 * this will let the clients know whose is the turn
	 */
	public void turnInfo() {
		
		ClientHandler.broadcast(new ServerMessage(ServerCommands.TURN_INFO));
		
	}
	
	@Override
	/**
	 * this will send message to each clients log
	 */
	public void gameLogMessage(String logMessage) {
		
		ServerMessage message = new ServerMessage(ServerCommands.GAME_LOG);
		List<String> info = new ArrayList<String>();
		info.add(logMessage);
		message.setInfolist(info);
		ClientHandler.broadcast(message);
		
	}
	
	@Override
	/**
	 * this will method will let the player knows when he make a revelation
	 */
	public void makeRevelation(Player player) {
		
		ServerMessage message = new ServerMessage(ServerCommands.REVELATION);
		player.getClientHandler().sendMessage(message);
		
	}	

	@Override
	/**
	 * this will notify the client sheet the new suspect that he have
	 */
	public void gameSuspectsChange(Player p) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.SUSPECTS);
		serverMessage.setInfolist(suspects(p));
		p.getClientHandler().sendMessage(serverMessage);

	}
	
	@Override
	/**
	 * this will notify the player card panel that the player have a new monk
	 */
	public void playerMonkCardChange(Player player) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.TAKE_MONK_CARDS);
		serverMessage.setInfolist(monks(player));
		player.getClientHandler().sendMessage(serverMessage);
		
	}

	@Override
	/**
	 * this will send to the user the answer from the client does he asked a feature
	 */
	public void answerFromAskedPlayerToClient(Player playerAsking,Player playerAnswering, String answerForplayerAsking,String answerForplayerAnswering) {
		
		ServerMessage message1 = new ServerMessage(ServerCommands.FINAL_ANSWER_FROM_QUESTION);
		List<String> info = new ArrayList<String>();
		info.add(answerForplayerAsking);
		message1.setInfolist(info);
		playerAsking.getClientHandler().sendMessage(message1);
		
		
		ServerMessage message2 = new ServerMessage(ServerCommands.FINAL_ANSWER_FROM_QUESTION);
		info = new ArrayList<String>();
		info.add(answerForplayerAnswering);
		message2.setInfolist(info);
		playerAnswering.getClientHandler().sendMessage(message2);
		
		
		if(playerAsking==game.getCurrentPlayer()) {
			ServerMessage serverMessage = new ServerMessage(ServerCommands.SECOND_QUESTION_ROW);
			List<String> infoToSend = new ArrayList<String>();
			infoToSend.add(playerAsking.getPlayerName());
			serverMessage.setInfolist(infoToSend);
			playerAnswering.getClientHandler().sendMessage(serverMessage);
		}
		
	}
	
	@Override
	/**
	 * return a feature to the user
	 */
	public void giveMeAFeature(Player player) {
		
		ServerMessage message = new ServerMessage(ServerCommands.GIVE_ME_A_FEATURE);
		player.getClientHandler().sendMessage(message);
		
	}
	
	@Override
	/**
	 * let the user to play a spetial card
	 */
	public void playThisCard(String cardName) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.PLAY_THIS_CARD);
		List<String> info = new ArrayList<String>();
		info.add(cardName);
		serverMessage.setInfolist(info);
		game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
		
	}
	
	@Override
	
	public void showMonksInPlayCardFrame(List<Monje> monksWithFeature) {
		
		List<String> lista = new ArrayList<String>();
		for (int i = 0; i < game.getCurrentPlayer().getHojaSospechosos().size(); i++)
			lista.add(i, "0");
		for (Monje monje : monksWithFeature) 
			lista.set(monje.getId(), "1");
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.SHOW_MONKS_IN_PLAY_CARD_FRAME);
		
		serverMessage.setInfolist(lista);
		game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
		
	}
	
	@Override
	/**
	 * this will send a message to a player
	 */
	public void messageForPlayer(Player p, String message) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.MESSAGE_FOR_PLAYER);
		List<String> info = new ArrayList<String>();
		info.add(message);
		serverMessage.setInfolist(info);
		p.getClientHandler().sendMessage(serverMessage);
		
	}
	
	@Override
	/**
	 * this will send a message to all players 
	 */
	public void messageForAllPlayers(String message) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.MESSAGE_FOR_ALL_PLAYERS);
		List<String> info = new ArrayList<String>();
		info.add(message);
		serverMessage.setInfolist(info);
		ClientHandler.broadcast(serverMessage);
		
	}
	
	@Override
	/**
	 * this will set a message to the imagePanel to show a card in the context
	 */
	public void messageForAllPlayersExceptThis(String message, Player p) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.MESSAGE_FOR_ALL_PLAYERS);
		List<String> info = new ArrayList<String>();
		info.add(message);
		serverMessage.setInfolist(info);
		p.getClientHandler().broadcastFromClient(serverMessage);
		
	}
	
	@Override
	/**
	 * this will add a player dices to the confesionario
	 */
	public void imageMessage(int cardType,int imageNumber, String message, String cardTitle,String cardText,boolean special) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.IMAGE_MESSAGE);
		List<String> info = new ArrayList<String>();
		info.add(String.valueOf(cardType));
		info.add(String.valueOf(imageNumber));
		info.add(message);
		info.add(cardTitle);
		info.add(cardText);
		info.add(String.valueOf(special));
		serverMessage.setInfolist(info);
		if(cardType == Constants.MONK_CARD_TYPE || cardType == Constants.PLAY_CARD_TYPE) {
			game.getCurrentPlayer().getClientHandler().sendMessage(serverMessage);
		}
		else if(cardType == Constants.MISA_CARD_TYPE) {
			ClientHandler.broadcast(serverMessage);
		}
	}
	
	@Override
	public void imageMessageForPlayer(int cardType, int imageNumber, String message,String cardTitle, String cardText, boolean special, Player p) {
		
		ServerMessage serverMessage = new ServerMessage(ServerCommands.IMAGE_MESSAGE);
		List<String> info = new ArrayList<String>();
		info.add(String.valueOf(cardType));
		info.add(String.valueOf(imageNumber));
		info.add(message);
		info.add(cardTitle);
		info.add(cardText);
		info.add(String.valueOf(special));
		serverMessage.setInfolist(info);
		p.getClientHandler().sendMessage(serverMessage);
	}

	//EVENTOS LISTENER

	
	
	//FUNCIONES PRIVADAS
	/**
	 * this is the constructor of the controler in singletone pattern
	 * @param game
	 */
	private Controller(Game game) {

		this.game = game;
		ClientHandler.setController(this);
		clientMessageList = new ArrayBlockingQueue<ClientMessageClientHandler>(10,true);

	}
	/**
	 * this will set the initial params to the players
	 */
	
	private void sendInitialInfoToPlayers() {
		
		for(Player p: Game.getInstance().getPlayerList()) {

			ServerMessage serverMessage = new ServerMessage(ServerCommands.GAME_STARTED);
			List<String> infoList = new ArrayList<String>();
			infoList.add(String.valueOf(p.getPlayerNumber()));
			infoList.add(p.getPlayerName());
			serverMessage.setInfolist(infoList);
			p.getClientHandler().sendMessage(serverMessage);

			gameSuspectsChange(p);

			playerMonkCardChange(p);

			serverMessage = new ServerMessage(ServerCommands.QUESTIONS);	
			p.getClientHandler().sendMessage(serverMessage);

		}
	}
	
	/**
	 * this will check is the name has been selected before by other user
	 * @param playerName
	 * @return
	 */
	private boolean checkName(String playerName) {

		boolean found = false;
		for(Player p:game.getPlayerList()) {
			if(playerName.equalsIgnoreCase(p.getPlayerName())) found = true;
		}
		return !found;
		
	}
	
	
	private Player getPlayerFromClient(ClientHandler client){
		
		Player player = null;
		Iterator<Player> itr = Game.getInstance().getPlayerList().iterator();
		boolean found = false;
		while(itr.hasNext() && !found)
		{
			player = itr.next();
			if(player.getClientHandler() == client) found = true;
		}
		return player;
	
	}
	
	/**
	 * this will returns each player info to the server 
	 */
	private void askPlayersForInfo() {
		
		ClientHandler.broadcast(new ServerMessage(ServerCommands.GIVE_ME_PLAYER_INFO));
	
	}
	
	/**
	 * this will send a move to the client to be moved
	 * @param destiny
	 */
	private void movePlayer(String destiny) {
		
		int square = Integer.parseInt(destiny);
		Player p = game.getCurrentPlayer();
		game.movePlayer(p, square);
		
	}
	
	/**
	 * this will make a check mark on the suspects sheet of the client 
	 * @param p client to update suspects
	 * @return
	 */
	private List<String> suspects(Player p) {
		
		List<String> lista = new ArrayList<String>();

		for (int i = 0; i < p.getHojaSospechosos().size(); i++)
			lista.add(i, "0");
		
		for (Suspect pibot : p.getHojaSospechosos())
			if (pibot.isMarcado()) {
				String name = pibot.getMonje().getName();
				switch (name.toLowerCase()) {
				case "matthew":
					lista.set(0, "1");
					break;
				case "william":
					lista.set(1, "1");
					break;
				case "fortune":
					lista.set(2, "1");
					break;
				case "harold":
					lista.set(3, "1");
					break;
				case "malachi":
					lista.set(4, "1");
					break;
				case "gerard":
					lista.set(5, "1");
					break;
				case "basil":
					lista.set(6, "1");
					break;
				case "thomas":
					lista.set(7, "1");
					break;
				case "galbraith":
					lista.set(8, "1");
					break;
				case "michael":
					lista.set(9, "1");
					break;
				case "emmanuel":
					lista.set(10, "1");
					break;
				case "cuthbert":
					lista.set(11, "1");
					break;
				case "jacques":
					lista.set(12, "1");
					break;
				case "bartholomew":
					lista.set(13, "1");
					break;
				case "andre":
					lista.set(14, "1");
					break;
				case "nicholas":
					lista.set(15, "1");
					break;
				case "bruno":
					lista.set(16, "1");
					break;
				case "sergio":
					lista.set(17, "1");
					break;
				case "berengar":
					lista.set(18, "1");
					break;
				case "julian":
					lista.set(19, "1");
					break;
				case "cyrille":
					lista.set(20, "1");
					break;
				case "charles":
					lista.set(21, "1");
					break;
				case "philippe":
					lista.set(22, "1");
					break;
				case "guy":
					lista.set(23, "1");
					break;

				}
			}
		return lista;
	}	
	/**
	 * this will add monks to cliends card panel
	 * @param p
	 * @return
	 */
	private List<String> monks(Player p) {

		List<String> lista = new ArrayList<String>();
		for (int i = 0; i < p.getHojaSospechosos().size(); i++)
			lista.add(i, "0");
		for (Monje monje : p.getMonkCards()) 
			lista.set(monje.getId(), "1");
		return lista;
		
	}
	//FUNCIONES PRIVADAS
	
}
